﻿using System;
using System.Collections.Generic;

namespace ETModel
{
	[ObjectSystem]
	public class OpcodeTypeComponentSystem : AwakeSystem<OpcodeTypeComponent>
	{
		public override void Awake(OpcodeTypeComponent self)
		{
			self.Load();
		}
	}
	
	[ObjectSystem]
	public class OpcodeTypeComponentLoadSystem : LoadSystem<OpcodeTypeComponent>
	{
		public override void Load(OpcodeTypeComponent self)
		{
			self.Load();
		}
	}

	public class OpcodeTypeComponent : Component
	{
		private readonly DoubleMap<int, Type> inTypes = new DoubleMap<int, Type>();
        private readonly Dictionary<int, IInMessage> inTypeMessages = new Dictionary<int, IInMessage>();

        private readonly DoubleMap<int, Type> outTypes = new DoubleMap<int, Type>();
        private readonly Dictionary<int, IOutMessage> outTypeMessages = new Dictionary<int, IOutMessage>();

        public void Load()
		{
			this.inTypes.Clear();
            this.inTypeMessages.Clear();
            this.outTypes.Clear();
            this.outTypeMessages.Clear();
			
			List<Type> types = Game.EventSystem.GetTypes(typeof(MessageAttribute));
			foreach (Type type in types)
            {
                object[] attrs = type.GetCustomAttributes(typeof(InMessageAttribute), false);
                if (attrs.Length > 0)
                {
                    if (typeof(IInMessage).IsAssignableFrom(type))
                    {
                        InMessageAttribute inMessageAttribute = attrs[0] as InMessageAttribute;
                        if (inMessageAttribute == null)
                        {
                            continue;
                        }
                        this.inTypes.Add(inMessageAttribute.ID, type);
                        this.inTypeMessages.Add(inMessageAttribute.ID, (IInMessage)Activator.CreateInstance(type));
                    }
                    else
                    {
                        Log.Error($"{type.Name}必须继承自IInMessage！");
                    }
                }
                attrs = type.GetCustomAttributes(typeof(OutMessageAttribute), false);
                if (attrs.Length > 0)
                {
                    if (typeof(IOutMessage).IsAssignableFrom(type))
                    {
                        OutMessageAttribute outMessageAttribute = attrs[0] as OutMessageAttribute;
                        if (outMessageAttribute == null)
                        {
                            continue;
                        }
                        this.outTypes.Add(outMessageAttribute.ID, type);
                        this.outTypeMessages.Add(outMessageAttribute.ID, (IOutMessage)Activator.CreateInstance(type));
                    }
                    else
                    {
                        Log.Error($"{type.Name}必须继承自IOutMessage！");
                    }
                }
            }
		}

		public int GetInputID(Type type)
		{
			return this.inTypes.GetKeyByValue(type);
		}

        public int GetOutputID(Type type)
        {
            return this.outTypes.GetKeyByValue(type);
        }

        public Type GetInputType(int id)
		{
			return this.inTypes.GetValueByKey(id);
		}

        public Type GetOutputType(int id)
        {
            return this.outTypes.GetValueByKey(id);
        }

        // 客户端为了0GC需要消息池，服务端消息需要跨协程不需要消息池
        public IInMessage GetInputInstance(int id)
		{
#if SERVER
			Type type = this.GetInputType(id);
			if (type == null)
			{
				// 服务端因为有人探测端口，有可能会走到这一步，如果找不到opcode，抛异常
				throw new Exception($"not found opcode: {opcode}");
			}
			return Activator.CreateInstance(type);
#else
            return this.inTypeMessages[id];
#endif
		}

        // 客户端为了0GC需要消息池，服务端消息需要跨协程不需要消息池
        public IOutMessage GetOutputInstance(int id)
        {
#if SERVER
			Type type = this.GetOutputType(id);
			if (type == null)
			{
				// 服务端因为有人探测端口，有可能会走到这一步，如果找不到opcode，抛异常
				throw new Exception($"not found opcode: {opcode}");
			}
			return Activator.CreateInstance(type);
#else
            return this.outTypeMessages[id];
#endif
        }

        public override void Dispose()
		{
			if (this.IsDisposed)
			{
				return;
			}

            this.inTypes.Clear();
            this.inTypeMessages.Clear();
            this.outTypes.Clear();
            this.outTypeMessages.Clear();

            base.Dispose();
		}
	}
}